﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Threading;

namespace Sinacor.OMM.FileTail
{
    public class FileVirtualization : IDisposable
    {
        private const int VIRTUALIZED_BLOCK_SIZE = 10240;
        private const string CHR1013 = "\r\n";

        private string _fileName;
        private FileStream _fileStream;

        private int _fileLength = 0;
        private int _lastFileLengthRead = 0;
        private int _linesToVirtualize = 0;
        private int _nextLineStartIndex = 0;
        private int _horizontalPosition = 0;

        private string _virtualizedContent = string.Empty;

        private bool _keepFollowing;
        private bool _refreshControl;
        private bool _fileOpened;

        public delegate void NextLineStartIndexChangedHandler(int nextLineStartIndex);
        public event NextLineStartIndexChangedHandler OnNextLineStartChanged;

        public delegate void FileSizeChangedHandler(int fileSize);
        public event FileSizeChangedHandler OnFileSizeChanged;

        public delegate void VirtualizedContentChangedHandler(string content);
        public event VirtualizedContentChangedHandler OnVirtualizedContentChanged;

        public delegate void FileNameChangedHandler(string filename);
        public event FileNameChangedHandler OnFileNameChanged;

        public delegate void EndOfFileReachedHandler(int filesize);
        public event EndOfFileReachedHandler OnEndOfFileReached;

        public FileVirtualization()
        {

        }

        #region Virtualizers

        public void SetLinesToVirtualize(int numLines)
        {
            _linesToVirtualize = numLines;
        }

        public void SetHorizontalPosition(int horizontalPostition)
        {
            _horizontalPosition = horizontalPostition;
        }

        public int VirtualizeFirstLines(int horizontalPostition)
        {
            if (_fileOpened)
            {
                //Lê o contúdo do arquivo a parti da posição ZERO
                string contentTemp = ReadFileAfterPosition(0, (int)_fileStream.Length);

                //Virtualiza o número de linhas definido dentro do conteudo lido
                _virtualizedContent = VirtualizeLines(contentTemp, _linesToVirtualize);

                // Virtualiza horizontamente conforme a ScrollBar Horizontal
                VirtualizedContent = VirtualizeColums(_virtualizedContent, horizontalPostition);

                //Define o indice da próxima linha no ponteiro global do arquivo para ser usado nas próximas leituras
                NextLineStartIndex = FindNextLineStartPosition(contentTemp);

                //Retorna esse indice para ser usado na scrollBar
                return NextLineStartIndex;
            }
            else
                return 0;
        }

        public int VirtualizeNextLine(int horizontalPostition)
        {
            if (_fileOpened)
            {

                // Lê o arquivo a partir do indice da próxima linha definida no um ponteiro global de controle
                string contentTemp = ReadFileAfterPosition(_nextLineStartIndex, (int)_fileStream.Length);

                //Virtualiza o número de linhas definido dentro do conteudo lido
                _virtualizedContent = VirtualizeLines(contentTemp, _linesToVirtualize);

                //Virtualiza o conteúdo a partir da coluna definida (caso de Scroll horizontal)
                VirtualizedContent = VirtualizeColums(_virtualizedContent, horizontalPostition);

                //Define, o índice na próxima linha a ser lida, para controle no ponteiro global.
                int nextLinePositionTemp = FindNextLineStartPosition(contentTemp);
                if (nextLinePositionTemp >= CHR1013.Length)
                    NextLineStartIndex = _nextLineStartIndex + nextLinePositionTemp;

                //Retorna esse indice para ser usado na scrollBar
                return NextLineStartIndex;
            }
            else
                return 0;

        }

        public int VirtualizePreviousLine(int horizontalPostition)
        {
            if (_fileOpened)
            {
                // Le o arquivo para trás do ponteiro global definido
                string contentTemp = ReadFileBeforePosition(_nextLineStartIndex);

                //Define onde seria meu ponteiro após a leitura de um bloco de traz para frente
                int newStartPointTemp = _nextLineStartIndex - contentTemp.Length;

                // Encontra o ponteiro de ínicio da linha anterior
                NextLineStartIndex = newStartPointTemp + FindPreviousLineStartPosition(contentTemp);

                //Virtualia linhas a partir dessa nova posição encontrada
                VirtualizeNextLine(horizontalPostition);

                //Retorna o novo índice para uso na ScrollBar
                return NextLineStartIndex;
            }
            else
                return 0;
        }

        public int VirtualizeAfterPostion(int verticalPosition, int horizontalPostition)
        {
            if (_fileOpened)
            {
                //Lê o arquivo a partir de uma posição qualquer passada como parâmetro
                string contentTemp = ReadFileAfterPosition(verticalPosition, (int)_fileStream.Length);

                //Encontra o início da próxima linha a partir da posição passada como parâmetro
                int nextLinePositionTemp = FindNextLineStartPosition(contentTemp);

                //Define conteúdo temporário a partir do início de uma linha
                contentTemp = contentTemp.Substring(nextLinePositionTemp);

                //Virtualiza o número de linhas definido dentro do conteudo lido
                _virtualizedContent = VirtualizeLines(contentTemp, _linesToVirtualize);

                //Virtualiza o conteúdo a partir da coluna definida (caso de Scroll horizontal)
                VirtualizedContent = VirtualizeColums(_virtualizedContent, horizontalPostition);

                //Posiciona o ponteiro global na próxima linha a ser exibida
                NextLineStartIndex = verticalPosition + nextLinePositionTemp;

                //Retorna esse indice para ser usado na scrollBar
                return NextLineStartIndex;
            }
            else
                return 0;
        }

        public int VirtualizeBeforePostion(int verticalPosition, int horizontalPostition)
        {
            if (_fileOpened)
            {
                //Lê o arquivo a partir de uma posição qualquer passada como parâmetro
                string contentTemp = ReadFileBeforePosition(verticalPosition);

                // Encontra o ponteiro de ínicio da linha anterior dentro do arquivo
                int nextLineStartIndexTemp = verticalPosition - (contentTemp.Length - FindPreviousLineStartPosition(contentTemp));

                // Virtualiza após a posição indicada
                NextLineStartIndex = VirtualizeAfterPostion(nextLineStartIndexTemp, horizontalPostition);

                return NextLineStartIndex;
            }
            else
                return 0;
        }

        public int VirtualizeHorizontal(int horizontalPostition)
        {
            if (_fileOpened)
            {
                //Lê o arquivo antes da posição do ponteiro global
                string contentTemp = ReadFileBeforePosition(_nextLineStartIndex);

                //Acha o indice da linha anterior ao ponteiro
                int actualLineStartIndex = FindActualLineStartIndex(contentTemp);

                // Definie onde é o novo índice a partir do começo do arquivo
                actualLineStartIndex = (_nextLineStartIndex - contentTemp.Length) + actualLineStartIndex;

                //Le o arquivo a partir da linha atual
                contentTemp = ReadFileAfterPosition(actualLineStartIndex, (int)_fileStream.Length);

                //Virtualiza o número de linhas definido dentro do conteudo lido
                _virtualizedContent = VirtualizeLines(contentTemp, _linesToVirtualize);

                //Virtualiza o conteúdo a partir da coluna definida (caso de Scroll horizontal)
                VirtualizedContent = VirtualizeColums(_virtualizedContent, horizontalPostition);

                //Retorna o mesmo índice para uso na ScrollBar
                return NextLineStartIndex;
            }
            else
                return 0;
        }


        public int VirtualizeLastLines(int horizontalPostition)
        {
            if (_fileOpened)
            {
                //Fixa o tamanho do arquivo para toda esta virtualização
                int fileSize = (int)_fileStream.Length;

                // Lê bloco do final do arquivo para traz
                string contentTemp = ReadFileBeforePosition(fileSize);

                // Procura o offset da linha correspondente ao numero de linhas que é necessário virtualizar
                int indexToStartRead = FindLinePositionBackward(contentTemp, _linesToVirtualize);

                // Lê a partir do indice definido
                contentTemp = contentTemp.Substring(indexToStartRead);

                //Virtualiza o número de linhas definido dentro do conteudo lido
                _virtualizedContent = VirtualizeLines(contentTemp, _linesToVirtualize);

                //Virtualiza o conteúdo a partir da coluna definida (caso de Scroll horizontal)
                VirtualizedContent = VirtualizeColums(_virtualizedContent, horizontalPostition);

                NextLineStartIndex = fileSize; // -indexToStartRead;

                return NextLineStartIndex;
            }
            else
                return 0;
        }
        #endregion

        #region File Manipulation


        private int FindLinePositionBackward(string content, int numberOfLines)
        {
            int ret = 0;
            if (content.EndsWith(CHR1013))
                content = content.Substring(0, content.Length - CHR1013.Length);

            for (int idx = 0; idx < numberOfLines; idx++)
            {
                ret = content.LastIndexOf(CHR1013);
                if (ret >= 0)
                {
                    content = content.Substring(0, ret);
                }
                else
                {
                    ret = 0;
                    break;
                }
            }

            if (ret > CHR1013.Length)
                ret += CHR1013.Length;

            return ret;
        }

        private string VirtualizeColums(string contentExactlyInLineStart, int horizontalColumPosition)
        {
            string ret = string.Empty;
            int ultimoEnterEncontrado = 0;
            int idxDepoisDoEnter = 0;
            int tamParaLer = 0;
            int ponteiroInicio = 0;

            if (horizontalColumPosition > 0)
            {
                while (ultimoEnterEncontrado < contentExactlyInLineStart.Length)
                {
                    idxDepoisDoEnter = contentExactlyInLineStart.IndexOf(CHR1013, idxDepoisDoEnter) + CHR1013.Length;

                    if (idxDepoisDoEnter < CHR1013.Length)
                    {
                        ret += contentExactlyInLineStart;
                        break;
                    }

                    tamParaLer = (idxDepoisDoEnter - ultimoEnterEncontrado) - horizontalColumPosition;

                    if (tamParaLer < 2)
                        tamParaLer = 2;

                    ponteiroInicio = idxDepoisDoEnter - tamParaLer;
                    ultimoEnterEncontrado = idxDepoisDoEnter;
                    ret += contentExactlyInLineStart.Substring(ponteiroInicio, tamParaLer);
                }
            }
            else
            {
                ret = contentExactlyInLineStart;
            }

            return ret;

        }

        private string VirtualizeLines(string contentExactlyInLineStart, int numLines)
        {
            string ret = string.Empty;
            int nextLineTemp;

            //Virtualiza apenas um certo número de linhas...
            for (int idx = 0; idx < numLines; idx++)
            {
                nextLineTemp = FindNextLineStartPosition(contentExactlyInLineStart);

                if (nextLineTemp >= 0)
                {
                    if (contentExactlyInLineStart.Length > 0)
                    {
                        ret = ret + contentExactlyInLineStart.Substring(0, nextLineTemp);
                        contentExactlyInLineStart = contentExactlyInLineStart.Substring(nextLineTemp);
                    }
                }
                else
                {
                    ret += contentExactlyInLineStart.Substring(0, contentExactlyInLineStart.Length);
                    break;
                }

            }


            return ret;
        }

        private int FindNextLineStartPosition(string content)
        {
            int nextLinePositionTemp = content.IndexOf(CHR1013);

            nextLinePositionTemp = nextLinePositionTemp + CHR1013.Length;

            return nextLinePositionTemp;
        }

        private int FindActualLineStartIndex(string content)
        {
            int actualStartLinePosition = 0;

            if (!content.EndsWith(CHR1013))
                content += CHR1013;

            for (int idx = 2; idx > 0; idx--)
            {
                actualStartLinePosition = content.LastIndexOf(CHR1013);
                if (actualStartLinePosition < 0)
                {
                    actualStartLinePosition = 0;
                    break;
                }
                content = content.Substring(0, actualStartLinePosition);
            }

            if (actualStartLinePosition < CHR1013.Length)
                actualStartLinePosition = 0;
            else
                actualStartLinePosition += 2;

            return actualStartLinePosition;
        }

        private int FindPreviousLineStartPosition(string content)
        {
            int previousStartLinePosition = 0;

            if (!content.EndsWith(CHR1013))
                content += CHR1013;

            for (int idx = 3; idx > 0; idx--)
            {
                previousStartLinePosition = content.LastIndexOf(CHR1013);
                if (previousStartLinePosition < 0)
                {
                    previousStartLinePosition = 0;
                    break;
                }
                content = content.Substring(0, previousStartLinePosition);
            }

            if (previousStartLinePosition > CHR1013.Length)
                previousStartLinePosition += 2;

            return previousStartLinePosition;
        }


        private string ReadFileAfterPosition(int position, int fileLength)
        {
            int sizeToRead = VIRTUALIZED_BLOCK_SIZE;

            if (position + sizeToRead > _fileLength)
                sizeToRead = fileLength - position;

            byte[] buffer = new byte[sizeToRead];
            _fileStream.Position = position;
            _fileStream.Read(buffer, 0, sizeToRead);

            return Encoding.Default.GetString(buffer);
        }

        private string ReadFileBeforePosition(int position)
        {
            int sizeToRead = VIRTUALIZED_BLOCK_SIZE;

            int startPosTemp = position;

            if (position - sizeToRead < 0)
                startPosTemp = 0;
            else
                startPosTemp = position - sizeToRead;

            sizeToRead = position - startPosTemp;

            byte[] buffer = new byte[sizeToRead];
            _fileStream.Position = startPosTemp;
            _fileStream.Read(buffer, 0, sizeToRead);

            return Encoding.Default.GetString(buffer);

        }
        #endregion

        #region Tail Follower

        public void StartTailFollower()
        {
            if (!_keepFollowing)
            {
                Thread thr = new Thread(new ThreadStart(KeepFollowing));
                thr.Start();
            }
        }

        public void StopTailFollower()
        {
            _keepFollowing = false;
        }

        private void KeepFollowing()
        {
            _keepFollowing = true;
            _refreshControl = true;
            try
            {
                while (_keepFollowing && _fileOpened && _fileStream != null)
                {
                    if ((_lastFileLengthRead != (int)_fileStream.Length) || _refreshControl)
                    {
                        VirtualizeLastLines(_horizontalPosition);
                        FileLength = (int)_fileStream.Length;
                        _lastFileLengthRead = FileLength;
                        _refreshControl = false;
                    }
                    Thread.Sleep(200);
                }
            }
            catch (Exception) { }
        }

        #endregion

        #region Abre e fecha arquivo
        public bool OpenFile()
        {
            if (_fileStream == null)
            {
                if (File.Exists(this.FileName))
                {
                    _fileStream = new FileStream(this.FileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
                    this.FileLength = (int)_fileStream.Length;
                    this.NextLineStartIndex = 0;
                    FileOpened = true;
                    return true;
                }
                return false;
            }
            else
                throw new InvalidOperationException("Arquivo já está aberto");


        }


        public void CloseFile()
        {
            StopTailFollower();
            if (_fileStream != null)
            {
                FileOpened = false;
                _fileStream.Close();
                _fileStream.Dispose();
                _fileStream = null;
                this.FileName = "";
                this.VirtualizedContent = "";
                this.FileLength = 0;
                this.NextLineStartIndex = 0;
            }
        }
        #endregion

        #region Propriedades


        public bool RefreshControl
        {
            get { return _refreshControl; }
            set { _refreshControl = value; }
        }

        public int NextLineStartIndex
        {
            set
            {
                _nextLineStartIndex = value;
                OnNextLineStartIndexChangedDispatcher(value);
            }

            get { return _nextLineStartIndex; }
        }

        public int FileLength
        {
            set
            {
                _fileLength = value;
                OnFileSizeChangedDispatcher(value);
            }
            get { return _fileLength; }
        }

        public string VirtualizedContent
        {
            set
            {
                _virtualizedContent = value;
                OnVirtualizedContentChangedDispatcher(value);
            }
            get { return _virtualizedContent; }
        }


        public string FileName
        {
            set
            {
                _fileName = value;
                OnFileNameChangedDispatcher(value);
            }
            get { return _fileName; }
        }

        public bool FileOpened
        {
            get { return _fileOpened; }
            set { _fileOpened = value; }
        }
        #endregion

        #region Disparadores de Eventos
        private void OnFileSizeChangedDispatcher(int fileLengh)
        {
            try
            {
                if (OnFileSizeChanged != null)
                    OnFileSizeChanged(fileLengh);
            }
            catch (Exception) { }
        }


        private void OnVirtualizedContentChangedDispatcher(string virtualizedContent)
        {
            try
            {
                if (OnVirtualizedContentChanged != null)
                    OnVirtualizedContentChanged(virtualizedContent);
            }
            catch (Exception) { }
        }


        private void OnNextLineStartIndexChangedDispatcher(int nextLineStartIndex)
        {
            try
            {
                if (OnNextLineStartChanged != null)
                    OnNextLineStartChanged(nextLineStartIndex);
            }
            catch (Exception) { }
        }

        private void OnFileNameChangedDispatcher(string fileName)
        {
            try
            {
                if (OnFileNameChanged != null)
                    OnFileNameChanged(fileName);
            }
            catch (Exception) { }
        }

        private void OnEndOfFileReachedDispatcher(int fileSize)
        {
            try
            {
                if (OnEndOfFileReached != null)
                    OnEndOfFileReached(fileSize);
            }
            catch (Exception) { }

        }

        #endregion

        public void Dispose()
        {
            CloseFile();
        }
    }
}
